// License: http://www.codeplex.com/entlibcontrib/Project/License.aspx
// Author: Randy

using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Security.Permissions;

namespace EntLibContrib.Logging.TraceListeners
{
    /// <summary>
    /// Alternative to <see cref="System.Diagnostics.TextWriterTraceListener"/> with No GUID in the file name.
    /// </summary>
    /// <remarks>
    /// Does not need to inherit from <see cref="System.Diagnostics.TextWriterTraceListener"/>,
    /// which would involve defining another private string fileName member.
    /// </remarks>
    [HostProtection(SecurityAction.LinkDemand, Synchronization = true)]
    public class NoGuidTextWriterTraceListener : TraceListener
    {
        private string fileName;
        private TextWriter writer;
        /// <summary>
        /// Max Instances in for loop appending count to file name, where 0 is no max
        /// </summary>
        protected byte maxInstances = 1;  // only one instance
        /// <summary>
        /// Throw Exception if Max Instances reached
        /// </summary>
        protected bool throwMax = false;  // do not throw Exception

        /// <summary>
        ///     Initializes a new instance.
        ///     with the default TextWriter as the output recipient.
        /// </summary>
        public NoGuidTextWriterTraceListener()
        {
        }
        /// <summary>
        /// Initializes a new instance with a <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        public NoGuidTextWriterTraceListener(Stream stream)
            : this(stream, string.Empty)
        {
        }
        /// <summary>
        /// Initializes a new instance with a <see cref="TextWriter"/>.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        public NoGuidTextWriterTraceListener(TextWriter writer)
            : this(writer, string.Empty)
        {
        }
        /// <summary>
        /// Initializes a new named instance using the file name as the recipient.
        /// </summary>
        /// <param name="fileName">The file name to write to.</param>
        public NoGuidTextWriterTraceListener(string fileName)
        {
            this.fileName = fileName;
        }
        /// <summary>
        /// Initializes a new named instance with a <see cref="Stream"/>.
        /// </summary>
        /// <param name="stream">The stream to write to.</param>
        /// <param name="name">The name.</param>
        public NoGuidTextWriterTraceListener(Stream stream, string name)
            : base(name)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }
            this.writer = new StreamWriter(stream);
        }
        /// <summary>
        /// Initializes a new named instance with a <see cref="TextWriter"/>.
        /// </summary>
        /// <param name="writer">The writer to write to.</param>
        /// <param name="name">The name.</param>
        public NoGuidTextWriterTraceListener(TextWriter writer, string name)
            : base(name)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }
            this.writer = writer;
        }
        /// <summary>
        /// Initializes a new named instance with a file name.
        /// </summary>
        /// <param name="fileName">The file name to write to.</param>
        /// <param name="name">The name.</param>
        public NoGuidTextWriterTraceListener(string fileName, string name)
            : base(name)
        {
            this.fileName = fileName;
        }

        /// <summary>
        /// Closes the <see cref="TextWriter"/> so that it no longer receives tracing or debugging output.
        /// </summary>
        public override void Close()
        {
            if (this.writer != null)
            {
                this.writer.Close();
            }
            this.writer = null;
            base.Close();
        }
        /// <summary>
        /// Disposes this object.
        /// </summary>
        /// <param name="disposing">true to release managed resources</param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (disposing)
                {
                    if (this.writer != null)
                    {
                        this.writer.Dispose();
                    }
                    this.writer = null;
                }
            }
            finally
            {
                base.Dispose(disposing);
            }
        }
        /// <summary>
        /// Flushes the output buffer for the <see cref="TextWriter"/>.
        /// </summary>
        public override void Flush()
        {
            if (this.EnsureWriter())
            {
                this.writer.Flush();
            }
        }

        private static Encoding GetEncodingWithFallback(Encoding encoding)
        {
            Encoding encoding2 = (Encoding)encoding.Clone();
            encoding2.EncoderFallback = EncoderFallback.ReplacementFallback;
            encoding2.DecoderFallback = DecoderFallback.ReplacementFallback;
            return encoding2;
        }

        /// <summary>
        /// Writes a message to this instance's <see cref="TextWriter"/>.
        /// </summary>
        /// <param name="message">A text message to write.</param>
        public override void Write(string message)
        {
            if (this.EnsureWriter())
            {
                if (base.NeedIndent)
                {
                    this.WriteIndent();
                }
                this.writer.Write(message);
            }
        }

        /// <summary>
        /// Writes a message to this instance's <see cref="TextWriter"/>
        /// followed by a line terminator. The default line terminator is a carriage
        /// return followed by a line feed (\r\n).
        /// </summary>
        /// <param name="message"></param>
        public override void WriteLine(string message)
        {
            if (this.EnsureWriter())
            {
                if (base.NeedIndent)
                {
                    this.WriteIndent();
                }
                this.writer.WriteLine(message);
                base.NeedIndent = true;
            }
        }

        /// <summary>
        /// Gets or sets the text writer that receives the tracing or debugging output.
        /// </summary>
        public TextWriter Writer
        {
            get
            {
                this.EnsureWriter();
                return this.writer;
            }
            set
            {
                this.writer = value;
            }
        }

        private class NewName
        {
            private string directory;
            private string fileNameWithoutExtension;
            private string extension;
            public NewName(string fileName)
            {
                directory = Path.GetDirectoryName(fileName);
                fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
                extension = Path.GetExtension(fileName);
            }
            public string GetPath(int i)
            {
                return Path.Combine(directory, fileNameWithoutExtension + "_" + i + extension);
            }
        }
        // Suppress FxCop warning message for try-catch block of all errors
        // because an unusualy error in logging should not stop the application from running
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private bool EnsureWriter()
        {
            bool flag = true;
            if (writer == null)
            {
                // Create this Writer using specified file name similar to TextWriterTraceListener
                flag = false;
                if (this.fileName == null)
                {
                    return flag;
                }
                Encoding encodingWithFallback = GetEncodingWithFallback(new UTF8Encoding(false));
                const int bufferSize = 0x1000;
                NewName oNewName = null;
                for (int i = 0; i < this.maxInstances || this.maxInstances == 0; i++)
                {
                    try
                    {
                        if (i > 0)
                        {
                            if (oNewName == null)
                            {
                                oNewName = new NewName(this.fileName);
                            }
                            writer = new StreamWriter(oNewName.GetPath(i), true, encodingWithFallback, bufferSize);
                        }
                        else
                        {
                            writer = new StreamWriter(this.fileName, true, encodingWithFallback, bufferSize);
                        }
                        flag = true;
                        break;
                    }
                    catch (IOException)
                    {
                        // Occurs when file is open in another process
                        // Ignore error and try next file name in the for loop
                    }
                    catch (UnauthorizedAccessException)
                    {
                        break;
                    }
                    catch (Exception)
                    {
                        break;
                    }
                }
                if (!flag)
                {
					string fileNameForException = this.fileName;
                    this.fileName = null;
                    if (this.throwMax)
                    {
                        throw new LoggingMaxInstancesException(
                            "Reached maximum number of instances for log file named " +
							fileNameForException);
					}
                }
            }
            return flag;
        }
    }
}
